﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MonoStrategy
{
    /// <summary>
    /// Currently there are only settlers.
    /// </summary>
    public enum MovableType : int
    {
        Settler = 1,
        Constructor = 7,
        Soldier = 9,
        Grader = 10,
    }

    public interface IPositionTracker
    {
        CyclePoint Position { get; }
        event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
    }

    /// <summary>
    /// Intended to provide some default behavior for all essentially non-movable objects,
    /// which still are renderable, like resources or buildings.
    /// </summary>
    public class PositionTracker : IPositionTracker
    {
        private CyclePoint m_Position;
        public CyclePoint Position
        {
            get { return m_Position; }
            set
            {
                bool hasListener = (OnPositionChanged != null);
                Point old = new Point();

                if (hasListener)
                    old = m_Position.ToPoint();

                m_Position = value;

                if (hasListener && ((old.X != value.XGrid) || (old.Y != value.YGrid)))
                    OnPositionChanged(this, CyclePoint.FromGrid(old), value);
            }
        }
        public Object UserContext { get; set; }

        public event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
    }

    /// <summary>
    /// A path node contains all necessary information to provide animation,
    /// planning and cooperation.
    /// </summary>
    internal class MovablePathNode
    {
        internal static KeyComparer Comparer = new KeyComparer();

        internal class KeyComparer : IComparer<MovablePathNode>
        {
            public int Compare(MovablePathNode a, MovablePathNode b)
            {
                if (a.Position.X < b.Position.X)
                    return -1;
                else if (a.Position.X > b.Position.X)
                    return 1;

                if (a.Position.Y < b.Position.Y)
                    return -1;
                else if (a.Position.Y > b.Position.Y)
                    return 1;

                // must be the last comparison
                if (a.Time < b.Time)
                    return -1;
                else if (a.Time > b.Time)
                    return 1;

                return 0;
            }
        }

        /// <summary>
        /// The corresponding key for the internal space-time map.
        /// </summary>
        internal Point Position { get; set; }
        internal long Time { get; set; }
        /// <summary>
        /// A direction of animation, if any.
        /// </summary>
        internal Direction? Direction { get; set; }
        /// <summary>
        /// A path node is idle, if it has no direction.
        /// </summary>
        internal Boolean IsIdle { get { return !Direction.HasValue; } }
        /// <summary>
        /// The movable owning this path node.
        /// </summary>
        internal Movable Movable { get; set; }

        public override string ToString()
        {
            return Position + "; Time: " + Time + "; Dir: " + Direction;
        }
    }

    /// <summary>
    /// The base class for all movables. 
    /// </summary>
    public class Movable : IPositionTracker
    {
        internal static readonly Object GlobalLock = new object();

        private CyclePoint m_Position;
        private GenericJob m_Job;
        private Resource? m_Carrying;
        private readonly UniqueIDObject m_Unique;

        internal long UniqueID { get { return m_Unique.UniqueID; } }

        /// <summary>
        /// The current path node. TODO: In future this should be a read-only property
        /// which efficiently determines its value instead of being set explicitly.
        /// </summary>
        internal LinkedListNode<MovablePathNode> CurrentNode { get; set; }
        /// <summary>
        /// A list of path nodes currently in the queue.
        /// </summary>
        internal LinkedList<MovablePathNode> Path { get; private set; }
        /// <summary>
        /// The movable type. Currently ignored, but later this will provide ways to,
        /// for example, letting soldiers walk over snow while settlers couldn't.
        /// </summary>
        public MovableType MovableType { get; set; }
        /// <summary>
        /// The time when the engine should issue a new path. This is usually the time
        /// of the last path node.
        /// </summary>
        internal long ReplanTime
        {
            get
            {
                return Path.Last.Value.Time;
            }
        }
        /// <summary>
        /// This instance will take <see cref="CycleSpeed"/> times the <see cref="MovableManager.CycleResolution"/>
        /// to pass one grid cell. Currently this value is readonly and hardcoded to one (due to lack of support
        /// of the path engine, since it would make much things unnecessary complicated which should be avoided
        /// as long as the path engine itself is not feature complete; and to be honest it is already complicated enough
        /// to understand cooperative path planning even without having to think of different speeds and sizes; to
        /// be precise in theory it is easy but we are using a highly optimized version that additionally has a discrete 
        /// space-time and is 100% deterministic).
        /// </summary>
        public int CycleSpeed { get; private set; }
        /// <summary>
        /// This is the grid cell where this instance is being moved to.
        /// </summary>
        internal Point PathTarget { get; set; }
        /// <summary>
        /// The movable manager this instance is attached to (or null).
        /// </summary>
        internal MovableManager Parent { get; set; }
        /// <summary>
        /// An optional handler being called when the movable has reached its target
        /// or is stopped.
        /// </summary>
        internal Procedure<bool> ResultHandler { get; set; }

        internal bool IsInvalidated { get; set; }

        public bool IsMarkedForRemoval { get; private set; }

#if DEBUG
        internal System.Diagnostics.StackTrace PathStackTrace { get; set; }
#endif

        public event DChangeHandler<Movable, GenericJob> OnJobChanged;

        public GenericJob Job
        {
            get
            {
                return m_Job;
            }

            internal set
            {
                GenericJob backup = m_Job;

                if (value == m_Job)
                    return;

                m_Job = value;

                if (backup != null)
                { 
                    // discard current job
                    backup.Dispose();
                }

                Stop();

                if (OnJobChanged != null)
                    OnJobChanged(this, backup, m_Job);
            }
        }

        public Object UserContext { get; set; }

        /// <summary>
        /// Is this instance currently carrying out a job?
        /// </summary>
        public Boolean HasJob { get { return Job != null; } }
        /// <summary>
        /// If any, the resource currently carried (an eventually dropped) by this instance.
        /// </summary>
        internal Resource? Carrying
        {
            get
            {
                return m_Carrying;
            }

            set
            {
                var backup = m_Carrying;

                m_Carrying = value;

                if ((value != backup) && (OnCarryingChanged != null))
                    OnCarryingChanged(this);
            }
        }
        public CyclePoint Position
        {
            get { return m_Position; }
        }

        /// <summary>
        /// Well yes, you must not do this. This method is only exported for the path planning engine and calling it
        /// outside the right context may immediately crash the game! There is simply NO way to change a movable's
        /// position without the path planning engine or by passing a proper position in the constructor. Well to be
        /// true, there are rare exceptions but you have to be extremely careful and to know what you are doing...
        /// </summary>
        /// <param name="inNewPosition"></param>
        internal void SetPosition_YouMustNotDoThis(CyclePoint inNewPosition)
        {
            var backup = m_Position;

            m_Position = inNewPosition;

            if (backup.ToPoint() != inNewPosition.ToPoint())
            {
                // notify grid cell change
                RaisePositionChange(backup);
            }
        }

        /// <summary>
        /// Is movable eligable for new jobs?
        /// </summary>
        internal bool IsIdle { get { return !HasJob;/* && !UserControlable;*/ } }
        internal bool UserControlable { get; private set; }

        /// <summary>
        /// Is raised by <see cref="MovableManager.ProcessCycle"/> whenever it changes the movable position.
        /// </summary>
        public event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
        /// <summary>
        /// Is raised whenever <see cref="Stop"/> is called.
        /// </summary>
        public event Procedure<Movable> OnStop;
        public event Procedure<Movable> OnCarryingChanged;

        internal Movable(CyclePoint inInitialPosition, bool inUserControlable)
        {
            m_Unique = new UniqueIDObject(this);
            m_Position = CyclePoint.FromGrid(new Point((int)inInitialPosition.X, (int)inInitialPosition.Y));
            Path = new LinkedList<MovablePathNode>();
            PathTarget = inInitialPosition.ToPoint();
            MovableType = MovableType.Settler;
            CycleSpeed = 1;
            UserControlable = inUserControlable;
        }


        /// <summary>
        /// Is raised by <see cref="MovableManager.ProcessCycle"/> whenever it changes the movable position.
        /// </summary>
        internal void RaisePositionChange(CyclePoint inOldValue)
        {
            if (OnPositionChanged != null)
                OnPositionChanged(this, inOldValue, Position);
        }

        /// <summary>
        /// An object only is considered moving if its current path node got a direction, the only case in 
        /// which true is returned.
        /// </summary>
        public Boolean IsMoving
        {
            get {
                return ((CurrentNode != null) && (CurrentNode.Value.Direction != null) && CurrentNode.Value.Direction.HasValue) ? true : false;
            }
        }

        /// <summary>
        /// Stops movable in next aligned cycle, causing it to drop resources being carried (if any),
        /// and becoming idle.
        /// </summary>
        internal void Stop()
        {
            lock (Movable.GlobalLock)
            {
                var handler = ResultHandler;
                PathTarget = Position.ToPoint();

                if (handler != null)
                {
                    ResultHandler = null;

                    handler(false);
                }

                IsInvalidated = true;
            }

            if (OnStop != null)
                OnStop(this);
        }

        public void InterpolateMovablePosition(
            ref Direction refDirection,
            out CyclePoint outXYInterpolation,
            out double outZInterpolation)
        {
            outXYInterpolation = Position;
            outZInterpolation = 0;

            if(Parent != null)
                Parent.InterpolateMovablePosition(this, ref refDirection, out outXYInterpolation, out outZInterpolation);
        }
        

        internal void Dispose()
        {
            IsMarkedForRemoval = true;
            Stop();
        }
    }
}
